tpm2: Support safely rotating the lockout hierarchy authorization value#539
Conversation
b48db59 to
3c9ab81
Compare
This updates the Connection.EnsureProvisioned API to safely support rotating the authorization value for the lockout hierarchy, which can be used during reprovisioning. This is particularly required during a factory reset, when secrets should be rotated. The lockout hierarchy is used on successful boots to reset the DA counter. A single authorization failure makes the lockout hierarchy unavailable for the preprogrammed recovery time (currently 24 hours). This makes it challenging to update the value because it's not possible to atomically update both the value in the TPM and the value stored in persistent storage. This PR works around this by using a transient authorization policy that permits the use of the TPM2_HierarchyChangeAuth command with a signed assertion (TPM2_PolicySigned), using a key that's derived from the original authorization value. An authorization policy is used by default for other uses of the lockout hierarchy as this provides a way to detect a transient state resulting from a previously interrupted update before using the incorrect authorization value. The WithProvisionNewLockoutAuthValue option no longer allows an authorization value to be specified directly. One is derived automatically from the supplied random source, with the length determined by the value of the TPM_PT_CONTEXT_HASH property. A callback is supplied so that the authorization data can be stored to persistent storage. This happens several times during an update.
3c9ab81 to
cf97c51
Compare
| // | ||
| // This option will also resume a previously interrupted update, as long as the most recent authorization | ||
| // data is supplied to [WithLockoutAuthData]. | ||
| func WithProvisionNewLockoutAuthValue(rand io.Reader, syncData func([]byte) error) EnsureProvisionedOption { |
There was a problem hiding this comment.
Would it make more sense to call it WithProvisionNewLockoutAuthData (Data not Value) so people do not think they should call WithLockoutAuthValue. Or maybe use some types for []byte to make them distinct.
There was a problem hiding this comment.
That sounds ok.
| case tpm2.IsTPMWarning(err, tpm2.WarningLockout, command): | ||
| return ErrTPMLockout | ||
| case tpm2.IsTPMSessionError(err, tpm2.ErrorPolicyFail, command, 1): | ||
| return ErrInvalidLockoutAuthPolicy |
There was a problem hiding this comment.
It's impossible to hit this case now because authorizeLockout returns ErrInvalidLockoutAuthPolicy instead.
| return nil | ||
| } | ||
|
|
||
| opts := []EnsureProvisionedOption{WithLockoutAuthValue(nil), WithProvisionNewLockoutAuthValue(bytes.NewReader(data.lockoutAuthBytes), syncLockoutAuthData)} |
There was a problem hiding this comment.
these options is what to try if we didn't have a stored auth data/params yet?
There was a problem hiding this comment.
Yes, although perhaps there should be an explicit WithUnconfiguredLockoutAuth option instead.
| case params.AuthPolicy != nil && session.Handle().Type() == tpm2.HandleTypeHMACSession: | ||
| // authorization was performed with a HMAC session when we have policy data, | ||
| // which only happens if the lockout hierarchy has no policy set. | ||
| return ErrLockoutAuthNotInitialized |
There was a problem hiding this comment.
tests don't' end up here
There was a problem hiding this comment.
Oh, that's weird - lockoutauthSuite.TestResetDictionaryAttackLockAuthPolicyUnset is meant to hit this.
| err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session, m.tpm.HmacSession().IncludeAttrs(tpm2.AttrCommandEncrypt)) | ||
| default: | ||
| // We're using HMAC auth | ||
| err = m.tpm.HierarchyChangeAuth(m.tpm.LockoutHandleContext(), m.authParams.NewAuthValue, session.IncludeAttrs(tpm2.AttrCommandEncrypt)) |
There was a problem hiding this comment.
this case is not reached by tests
There was a problem hiding this comment.
It turns out that it's not actually possible to hit this case, so I've removed it.
…rupted These are merged into a new error: ErrLockoutAuthInvalid. There's no point in having 2 different errors for conditions that require the same resolution (calling EnsureProvisioned again).
|
Ok, I've pushed some updates:
|
This updates the
Connection.EnsureProvisionedAPI to safely supportrotating the authorization value for the lockout hierarchy, which can be
used during reprovisioning. This is particularly required during a
factory reset, when secrets should be rotated.
The lockout hierarchy is used on successful boots to reset the DA
counter. A single authorization failure makes the lockout hierarchy
unavailable for the preprogrammed recovery time (currently 24 hours).
This makes it challenging to update the value because it's not possible
to atomically update both the value in the TPM and the value stored in
persistent storage.
This PR works around this by using a transient authorization policy that
permits the use of the
TPM2_HierarchyChangeAuthcommand with a signedassertion (
TPM2_PolicySigned), using a key that's derived from theoriginal authorization value. An authorization policy is used by default
for other uses of the lockout hierarchy as this provides a way to detect
a transient state resulting from a previously interrupted update before
using the incorrect authorization value.
The
WithProvisionNewLockoutAuthValueoption no longer allows anauthorization value to be specified directly. One is derived
automatically from the supplied random source, with the length
determined by the value of the
TPM_PT_CONTEXT_HASHproperty. A callbackis supplied so that the authorization data can be stored to persistent
storage. This happens several times during an update.